#pragma once

#include <string>
#include "export_match.h"
#include "type_factory.h"
#include <memory>
#include <functional>
#include <typeindex>

namespace nef {

class Catalog;

class IExport
{
public:
	typedef std::shared_ptr<IExport> Ptr;

	virtual ~IExport() { }

	const std::string &ContractName() const { return _contractName; }
	const std::type_index &TypeIndex() const { return _typeIdx; }

	template<typename T>
	bool IsMatch(const std::string &contractName, Catalog &cat, bool &retry) const
	{
		static std::type_index TYPE_INDEX = std::type_index(typeid(T));

		if (_typeIdx == TYPE_INDEX &&
			_contractName == contractName)
			   return true;

		if (HasMatch(cat, TYPE_INDEX, contractName))
			retry = true;

		return false;
	}

	template<typename T>
	std::shared_ptr<T> GetValue(Catalog &cat) const
	{
		const Export<T> *et = dynamic_cast<const Export<T> *>(this);

		if (et)
			return et->_fn(cat);
		else
			// It isn't an error condition if the types don't match. This can happen if
			// the contract is used by other types as well
			return std::shared_ptr<T>();
	}

protected:
	IExport(const std::string &contractName, const std::type_index &typeIdx)
		: _contractName(contractName), _typeIdx(typeIdx)
	{
	}

	virtual bool HasMatch(Catalog &cat, const std::type_index &idx, const std::string &contractName) const = 0;

private:
	std::string _contractName;
	std::type_index _typeIdx;
};

template<typename IxType>
class Export
	: public IExport
{
	friend class IExport;
	typedef std::function<std::shared_ptr<IxType> (Catalog &)> Getter;

public:
	Export(const std::string &contractName, const Getter &f)
		: IExport(contractName, std::type_index(typeid(IxType))), _fn(f), _alreadyExported(false)
	{
	}

	virtual bool HasMatch( Catalog &cat, const std::type_index &idx, const std::string &contractName ) const
	{
		if (_alreadyExported)
			return false;

		if (!has_export_match<IxType>(contractName, idx))
			return false;

		// This will force the export
		GetValue<IxType>(cat);

		_alreadyExported = true;
		return true;
	}

private:
	Getter _fn;
	mutable bool _alreadyExported;
};

template<typename T>
struct export_type_factory
	: type_factory<T>
{
	export_type_factory(const Catalog::Ptr &cat, const IExport::Ptr &xp)
		: _cat(cat), _export(xp)
	{
	}

	virtual std::shared_ptr<T> create() const
	{
		return _export->GetValue<T>(*_cat);
	}

private:
	Catalog::Ptr _cat;
	IExport::Ptr _export;
};

template<typename Scope>
inline IExport::Ptr CreateExport(const Scope &proxy, const std::string &contractName = NO_CONTRACT)
{
	typedef decltype(proxy(*((Catalog*)nullptr))) SpIxType;
	typedef SpIxType::element_type IxType;

	IExport::Ptr ex(new Export<IxType>(contractName, proxy));

	return ex;
}

}